using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Collections;

namespace needle.Services.astra.Collections
{
	/// <summary>
	/// List of items, which can be limitted to its subset.
	/// </summary>
	/// <typeparam name="T">Item type.</typeparam>
	public class SubsetList<T> : ISubsetList<T>, IItemsChangeList<T>
	{
		/// <summary>
		/// Used to set <see cref="SubsetList{}._listItemsChangeLock"/> value.
		/// </summary>
		private struct ListItemsChangeLockSetter : IDisposable
		{
			/// <summary>
			/// Increments <see cref="SubsetList{}._listItemsChangeLock"/>.
			/// </summary>
			/// <param name="list">List.</param>
			public ListItemsChangeLockSetter(SubsetList<T> list)
			{
				_list = list;
				_list._listItemsChangeLock++;
			}

			#region IDisposable Members

			/// <summary>
			/// Decrements <see cref="SubsetList{}._listItemsChangeLock"/>.
			/// </summary>
			public void Dispose()
			{
				_list._listItemsChangeLock--;
			}

			#endregion

			/// <summary>
			/// List.
			/// </summary>
			private SubsetList<T> _list;
		}


		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="list">See <see cref="List"/>.</param>
		/// <exception cref="ArgumentNullException"><paramref name="list"/> is null.</exception>
		public SubsetList(IList<T> list)
		{
			if (list == null)
				throw new ArgumentNullException("list");
			_list = list;
			IItemsChangeList<T> itemsChangeList = list as IItemsChangeList<T>;
			if (itemsChangeList != null)
				itemsChangeList.ItemsChange += new EventHandler<ListItemsChangeEventArgs<T>>(List_ItemsChange);
		}

		/// <summary>
		/// Wrapped full list.
		/// </summary>
		/// <remarks>If this list implements <see cref="IItemsChangeList{T}"/> interface, its list changes are propagated to this subset list.</remarks>
		public IList<T> List
		{
			get { return _list; }
		}

		/// <summary>
		/// Copies specified items to <paramref name="array"/>.
		/// </summary>
		/// <param name="index">Starting index of this list.</param>
		/// <param name="array">Target array.</param>
		/// <param name="arrayIndex">Starting index of target array from which items will be assigned.</param>
		/// <param name="count">Number of items to copy from this list.</param>
		/// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception>
		/// <exception cref="ArgumentException"><paramref name="array"/> does not have enough space or is multidimensional.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// <paramref name="index"/> is &lt; 0 or &gt;= <see cref="Count"/>.
		/// </exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// <paramref name="count"/> is &lt;= 0 or is bigger then available items from <paramref name="index"/> to the end of the list.
		/// </exception>
		/// <exception cref="ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is &lt; 0.</exception>
		public void CopyTo(int index, T[] array, int arrayIndex, int count)
		{
			if (index < 0)
				throw new ArgumentOutOfRangeException("index", index, "Must be >= 0.");
			if (index >= this.Count)
				throw new ArgumentOutOfRangeException("index", index, "Must be < Count.");
			if (count <= 0)
				throw new ArgumentOutOfRangeException("count", count, "Must be > 0.");
			if (count >= this.Count - index)
				throw new ArgumentOutOfRangeException("count", count, "So many items are not available.");
			if (array == null)
				throw new ArgumentNullException("array");
			if (array.Rank != 1)
				throw new ArgumentException("Must be one dimensional.", "array");
			if (arrayIndex < 0)
				throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, "Must be >= 0.");
			if (array.Length - arrayIndex < count)
				throw new ArgumentException("Does not have enough space.", "array");
			for (int i = 0; i < count; i++)
			{
				array[arrayIndex + i] = this[index + i];
			}
		}

		/// <summary>
		/// Whether <paramref name="index"/> is valid outer index.
		/// </summary>
		/// <param name="index">Index to be validated.</param>
		/// <param name="countIsOK">Whether <see cref="Count"/> is valid <paramref name="index"/> value.</param>
		/// <returns><paramref name="index"/> validity.</returns>
		protected bool IsValidIndex(int index, bool countIsOK)
		{
			if (countIsOK)
				return index >= 0 && index <= this.Count;
			else
				return index >= 0 && index < this.Count;
		}
		/// <summary>
		/// Calls <see cref="IsValidIndex"/>.
		/// </summary>
		/// <param name="index">Index to be validated.</param>
		/// <param name="countIsOK">Whether <see cref="Count"/> is valid <paramref name="index"/> value.</param>
		/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is invalid.</exception>
		protected void ValidateIndex(int index, bool countIsOK)
		{
			if (IsValidIndex(index, countIsOK))
				return;
			if (countIsOK)
				throw new ArgumentOutOfRangeException("index", index, "Must be in range <0, Count>.");
			else
				throw new ArgumentOutOfRangeException("index", index, "Must be in range <0, Count).");
		}

		#region Events

		#region IItemsChangeList<T> Members

		/// <summary>
		/// Fired when one of <see cref="ListItemsChanges"/> occures.
		/// </summary>
		public event EventHandler<ListItemsChangeEventArgs<T>> ItemsChange;

		#endregion

		/// <summary>
		/// Whether <see cref="OnItemsChange"/> will fire event.
		/// If false, <see cref="IItemsChangeList{T}.ItemsChange"/> event from item store should be ignored and not translated.
		/// </summary>
		protected bool AllowListItemsChange
		{
			get { return _listItemsChangeLock == 0; }
		}

		/// <summary>
		/// Fires <see cref="ItemsChange"/> event when <see cref="AllowListItemsChange"/> is true.
		/// Derived class should always call this base method when it overrides it.
		/// </summary>
		/// <param name="e">Event arguments.</param>
		protected virtual void OnItemsChange(ListItemsChangeEventArgs<T> e)
		{
			if (this.AllowListItemsChange && this.ItemsChange != null)
				this.ItemsChange(this, e);
		}
		/// <summary>
		/// Called when new item is inserted into the list.
		/// </summary>
		/// <remarks>Calls <see cref="OnItemsChange"/>.</remarks>
		/// <param name="index">Index of item.</param>
		/// <param name="item">Item.</param>
		protected virtual void OnItemInserted(int index, T item)
		{
			OnItemsChange(new ListItemsChangeEventArgs<T>(ListItemsChanges.Add, item, index));
		}
		/// <summary>
		/// Called when item is removed from the list.
		/// </summary>
		/// <remarks>Calls <see cref="OnItemsChange"/>.</remarks>
		/// <param name="index">Index of item before it was removed.</param>
		/// <param name="item">Item (which does not need to be activated).</param>
		protected virtual void OnItemRemoved(int index, T item)
		{
			OnItemsChange(new ListItemsChangeEventArgs<T>(ListItemsChanges.Remove, item, index));
		}
		/// <summary>
		/// Called when item is replaced in the list.
		/// </summary>
		/// <remarks>Calls <see cref="OnItemsChange"/>.</remarks>
		/// <param name="index">Index of item.</param>
		/// <param name="newItem">New item.</param>
		/// <param name="oldItem">Old item.</param>
		protected virtual void OnItemReplaced(int index, T newItem, T oldItem)
		{
			OnItemsChange(new ListItemsChangeEventArgs<T>(ListItemsChanges.Replace, newItem, oldItem, index));
		}
		/// <summary>
		/// Called when item is moved in the list.
		/// </summary>
		/// <remarks>Calls <see cref="OnItemsChange"/>.</remarks>
		/// <param name="newIndex">New index of item.</param>
		/// <param name="oldIndex">Old index of item.</param>
		/// <param name="item">Item.</param>
		protected virtual void OnItemMoved(int newIndex, int oldIndex, T item)
		{
			OnItemsChange(new ListItemsChangeEventArgs<T>(ListItemsChanges.Move, item, newIndex, oldIndex));
		}
		/// <summary>
		/// Called when the list changed dramatically (filtering, sorting, clearing, initializing, ...).
		/// </summary>
		/// <remarks>Calls <see cref="OnItemsChange"/>.</remarks>
		protected virtual void OnItemsReset()
		{
			OnItemsChange(new ListItemsChangeEventArgs<T>(ListItemsChanges.Reset));
		}

		/// <summary>
		/// Supresses <see cref="ItemsChange"/> event (see <see cref="OnItemsChange"/>).
		/// </summary>
		protected IDisposable SuppressListItemsChange()
		{
			return new ListItemsChangeLockSetter(this);
		}

		/// <summary>
		/// Translates <see cref="List"/>'s <see cref="IItemsChangeList{T}.ItemsChange"/> event.
		/// </summary>
		private void List_ItemsChange(object sender, ListItemsChangeEventArgs<T> e)
		{
			if (!this.AllowListItemsChange)
				return;
			int newIndex, oldIndex;
			switch (e.Change)
			{
				case ListItemsChanges.Add:
					newIndex = TranslateIndex(e.NewIndex, false);
					if (IsValidIndex(newIndex, true))
					{
						using (SuppressListItemsChange())
							IncrementEndIndex(1);
						OnItemInserted(newIndex, e.NewItem);
					}
					else
					{
						// subset was shifted downward by 1
						if (e.NewIndex < this.TStartIndex)
							OnItemsReset();
						else
							return;
					}
					break;
				case ListItemsChanges.Remove:
					oldIndex = TranslateIndex(e.OldIndex, false);
					if (IsValidIndex(oldIndex, false))
					{
						using (SuppressListItemsChange())
						{
							IncrementEndIndex(-1);
						}
						OnItemRemoved(oldIndex, e.OldItem);
					}
					else
					{
						// subset was shifted upward by 1
						if (e.OldIndex < this.TStartIndex)
							OnItemsReset();
						else
							return;
					}
					break;
				case ListItemsChanges.Replace:
					newIndex = TranslateIndex(e.NewIndex, false);
					if (!IsValidIndex(newIndex, false))
						return;
					OnItemReplaced(newIndex, e.NewItem, e.OldItem);
					break;
				case ListItemsChanges.Move:
					newIndex = TranslateIndex(e.NewIndex, false);
					oldIndex = TranslateIndex(e.OldIndex, false);
					bool newIndexIsValid = IsValidIndex(newIndex, false);
					bool oldIndexIsValid = IsValidIndex(oldIndex, false);
					if (newIndexIsValid && oldIndexIsValid)
					{
						OnItemMoved(newIndex, oldIndex, e.NewItem);
					}
					else if (newIndexIsValid)
					{
						if (e.OldIndex < this.TStartIndex)
							OnItemsReset();
						else
							goto case ListItemsChanges.Add;
					}
					else if (oldIndexIsValid)
					{
						if (e.NewIndex < this.TStartIndex)
							OnItemsReset();
						else
							goto case ListItemsChanges.Remove;
					}
					else
						return;
					break;
				case ListItemsChanges.Reset:
					using (SuppressListItemsChange())
					{
						EnsureSubsetRange(-1);
					}
					OnItemsReset();
					break;
			}
		}

		/// <summary>
		/// See <see cref="AllowListItemsChange"/>.
		/// </summary>
		private int _listItemsChangeLock;

		#endregion

		#region ISubsetListBase<T> Members

		/// <summary>
		/// See <see cref="ISubsetListBase{T}.TStartIndex"/>.
		/// </summary>
		public int TStartIndex
		{
			get
			{
				EnsureSubsetRange(-1);
				return _tStartIndex;
			}
			set
			{
				int tCount = this.TCount;
				EnsureSubsetRange(tCount);
				SetSubsetIndex(value, _tEndIndex, tCount);
			}
		}
		/// <summary>
		/// See <see cref="ISubsetListBase{T}.TEndIndex"/>.
		/// </summary>
		public int TEndIndex
		{
			get
			{
				int tCount = this.TCount;
				EnsureSubsetRange(tCount);
				return GetEndIndex(_tEndIndex, tCount);
			}
			set
			{
				int tCount = this.TCount;
				EnsureSubsetRange(tCount);
				SetSubsetIndex(_tStartIndex, value, tCount);
			}
		}
		/// <summary>
		/// See <see cref="ISubsetListBase{T}.FlatStartIndex"/>.
		/// </summary>
		public int FlatStartIndex
		{
			get { return this.TStartIndex; }
		}
		/// <summary>
		/// See <see cref="ISubsetListBase{T}.FlatEndIndex"/>.
		/// </summary>
		public int FlatEndIndex
		{
			get { return this.TEndIndex; }
		}
		/// <summary>
		/// See <see cref="ISubsetListBase{T}.SubsetCount"/>.
		/// </summary>
		public int SubsetCount
		{
			get
			{
				int tCount = this.TCount;
				EnsureSubsetRange(tCount);
				return GetEndIndex(_tEndIndex, tCount) - _tStartIndex + 1;
			}
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException("SubsetCount", value, "Must be >= 0.");
				if (value == int.MaxValue)
					this.TEndIndex = value;
				else
					this.TEndIndex = _tStartIndex + value - 1;
			}
		}
		/// <summary>
		/// See <see cref="ISubsetListBase{T}.TCount"/>.
		/// </summary>
		/// <value>Number of <see cref="List"/> items.</value>
		public int TCount
		{
			get { return this.List.Count; }
		}
		/// <summary>
		/// See <see cref="ISubsetListBase{T}.ResetSubset"/>.
		/// </summary>
		public void ResetSubset()
		{
			SetSubsetIndex(0, int.MaxValue);
		}
		/// <summary>
		/// See <see cref="ISubsetListBase{T}.SetSubsetIndex"/>.
		/// </summary>
		public void SetSubsetIndex(int startIndex, int endIndex)
		{
			SetSubsetIndex(startIndex, endIndex, -1);
		}
		/// <summary>
		/// See <see cref="ISubsetListBase{T}.SetSubsetCount"/>.
		/// </summary>
		public void SetSubsetCount(int startIndex, int subsetCount)
		{
			SetSubsetIndex(startIndex, startIndex + subsetCount - 1);
		}

		/// <summary>
		/// Translates <paramref name="index"/> between subset (<see cref="IList{}"/>) and <see cref="List"/>.
		/// </summary>
		/// <param name="index">Index.</param>
		/// <param name="fromSubset">
		/// If true, translates <paramref name="index"/> from subset to <see cref="List"/>.
		/// If false, translates <paramref name="index"/> from <see cref="List"/> to subset.
		/// </param>
		/// <returns>Translated index.</returns>
		protected int TranslateIndex(int index, bool fromSubset)
		{
			if (fromSubset)
				return index + this.TStartIndex;
			else
				return index - this.TStartIndex;
		}
		protected void EnsureSubsetRange(int tCount)
		{
			if (tCount < 0)
				tCount = this.TCount;
			if (
				!IsValidTStartIndex(_tStartIndex, _tEndIndex, tCount) ||
				!IsValidTEndIndex(_tStartIndex, _tEndIndex, tCount))
			{
				ResetSubset();
			}
		}
		/// <summary>
		/// Whether <paramref name="tStartIndex"/> is valid.
		/// </summary>
		/// <param name="tStartIndex">Start index in full <typeparamref name="T"/> items space.</param>
		/// <param name="tEndIndex">End index in full <typeparamref name="T"/> items space.</param>
		/// <param name="tCount">
		/// If &gt;= 0, this value is used instead of <see cref="TCount"/>, otherwise <see cref="TCount"/> is used.
		/// </param>
		protected bool IsValidTStartIndex(int tStartIndex, int tEndIndex, int tCount)
		{
			if (tStartIndex < 0)
				return false;
			if (tCount < 0)
				tCount = this.TCount;
			if (tCount == 0)
				return tStartIndex == 0;
			// tCount > 0
			else
				return tStartIndex < tCount && (tEndIndex == int.MaxValue || tStartIndex <= tEndIndex + 1);
		}
		/// <summary>
		/// Whether <paramref name="tEndIndex"/> is valid.
		/// </summary>
		/// <param name="tStartIndex">Start index in full <typeparamref name="T"/> items space.</param>
		/// <param name="tEndIndex">End index in full <typeparamref name="T"/> items space.</param>
		/// <param name="tCount">
		/// If &gt;= 0, this value is used instead of <see cref="TCount"/>, otherwise <see cref="TCount"/> is used.
		/// </param>
		protected bool IsValidTEndIndex(int tStartIndex, int tEndIndex, int tCount)
		{
			if (tEndIndex == int.MaxValue)
				return true;
			if (tCount < 0)
				tCount = this.TCount;
			return tEndIndex < tCount && (tStartIndex <= tEndIndex + 1);
		}
		/// <summary>
		/// Almost same as <see cref="ISubsetListBase{T}.SetSubsetIndex"/>, but can accept <paramref name="tCount"/> parameter value for optimization.
		/// </summary>
		/// <param name="tCount">
		/// If &gt;= 0, this value is used instead of <see cref="TCount"/>, otherwise <see cref="TCount"/> is used.
		/// </param>
		/// <exception cref="ArgumentOutOfRangeException">
		/// <paramref name="startIndex"/> or <paramref name="endIndex"/> is invalid.
		/// </exception>
		protected void SetSubsetIndex(int startIndex, int endIndex, int tCount)
		{
			if (_tStartIndex == startIndex && _tEndIndex == endIndex)
				return;
			if (tCount < 0)
				tCount = this.TCount;
			if (!IsValidTStartIndex(startIndex, endIndex, tCount))
			{
				throw new ArgumentOutOfRangeException("startIndex", startIndex,
					"Must be in range <0, Min(TCount - 1, endIndex + 1)>.");
			}
			if (!IsValidTEndIndex(startIndex, endIndex, tCount))
			{
				throw new ArgumentOutOfRangeException("endIndex", endIndex,
					"Must be in range <startIndex - 1, TCount) or int.MaxValue.");
			}
			_tStartIndex = startIndex;
			_tEndIndex = endIndex;
			OnItemsReset();
		}

		/// <summary>
		/// Gets end index of this subset (almost same as <see cref="TEndIndex"/> but without calling <see cref="EnsureSubsetRange"/>).
		/// </summary>
		/// <param name="endIndex">Raw end index.</param>
		/// <param name="tCount">
		/// If &gt;= 0, this value is used instead of <see cref="TCount"/>, otherwise <see cref="TCount"/> is used.
		/// </param>
		/// <returns>
		/// If <paramref name="endIndex"/> is <see cref="int.MaxValue"/>, returns (TCount or tCount) - 1, otherwise <paramref name="endIndex"/>.
		/// </returns>
		private int GetEndIndex(int endIndex, int tCount)
		{
			if (tCount < 0)
				tCount = this.TCount;
			if (endIndex == int.MaxValue)
				return tCount - 1;
			else
				return endIndex;
		}
		/// <summary>
		/// Increments (decrements) <see cref="TEndIndex"/> by given <paramref name="increment"/>.
		/// </summary>
		/// <remarks>
		/// Does nothing if <see cref="TEndIndex"/> was set with <see cref="int.MaxValue"/> or <paramref name="increment"/> is 0.
		/// Handles <see cref="OverflowException"/> if it occures.
		/// </remarks>
		/// <param name="increment">Increment to be added to <see cref="TEndIndex"/>.</param>
		private void IncrementEndIndex(int increment)
		{
			if (increment == 0 || _tEndIndex == int.MaxValue)
				return;
			int endIndex;
			try
			{
				checked
				{
					endIndex = _tEndIndex + increment;
				}
			}
			catch (OverflowException)
			{
				if (increment > 0)
					endIndex = int.MaxValue;
				else
					endIndex = _tStartIndex - 1;	//this will never happen ;-)
			}
			if (endIndex < _tStartIndex - 1)
				endIndex = _tStartIndex - 1;
			this.TEndIndex = endIndex;
		}

		/// <summary>
		/// See <see cref="TStartIndex"/>.
		/// </summary>
		private int _tStartIndex;
		/// <summary>
		/// See <see cref="TEndIndex"/>.
		/// </summary>
		private int _tEndIndex = int.MaxValue;

		#endregion

		#region IList<T> Members

		/// <summary>
		/// See <see cref="IList{T}.IndexOf"/>.
		/// </summary>
		public int IndexOf(T item)
		{
			int index = this.List.IndexOf(item);
			if (index >= 0)
			{
				if (index < this.TStartIndex || index > this.TEndIndex)
					index = -1;
				else
					index = TranslateIndex(index, false);
			}
			return index;
		}
		/// <summary>
		/// See <see cref="IList{T}.Insert"/>.
		/// </summary>
		public void Insert(int index, T item)
		{
			ValidateIndex(index, true);
			using (SuppressListItemsChange())
			{
				this.List.Insert(TranslateIndex(index, true), item);
				IncrementEndIndex(1);
			}
			OnItemInserted(index, item);
		}
		/// <summary>
		/// See <see cref="IList{T}.RemoveAt"/>.
		/// </summary>
		public void RemoveAt(int index)
		{
			ValidateIndex(index, false);
			T item = this[index];
			using (SuppressListItemsChange())
			{
				this.List.RemoveAt(TranslateIndex(index, true));
				IncrementEndIndex(-1);
			}
			OnItemRemoved(index, item);
		}
		/// <summary>
		/// See <see cref="IList{T}.Item[]"/>.
		/// </summary>
		public T this[int index]
		{
			get
			{
				ValidateIndex(index, false);
				return this.List[TranslateIndex(index, true)];
			}
			set
			{
				ValidateIndex(index, false);
				T oldItem = this[index];
				using (SuppressListItemsChange())
				{
					this.List[TranslateIndex(index, true)] = value;
				}
				OnItemReplaced(index, value, oldItem);
			}
		}

		#endregion

		#region ICollection<T> Members

		/// <summary>
		/// See <see cref="ICollection{T}.Add"/>.
		/// </summary>
		public void Add(T item)
		{
			Insert(this.Count, item);
		}
		/// <summary>
		/// See <see cref="ICollection{T}.Clear"/>.
		/// </summary>
		public void Clear()
		{
			using (SuppressListItemsChange())
			{
				int startIndex = 0;
				if (this.SubsetCount == this.TCount)
				{
					this.List.Clear();
				}
				else
				{
					startIndex = this.TStartIndex;
					for (int i = this.TEndIndex; i >= startIndex; i--)
						this.List.RemoveAt(i);
				}
				startIndex = this.TStartIndex;
				SetSubsetIndex(startIndex, _tEndIndex == int.MaxValue ? int.MaxValue : startIndex - 1);
			}
			OnItemsReset();
		}
		/// <summary>
		/// See <see cref="ICollection{T}.Contains"/>.
		/// </summary>
		public bool Contains(T item)
		{
			return IndexOf(item) >= 0;
		}
		/// <summary>
		/// See <see cref="ICollection{T}.CopyTo"/>.
		/// </summary>
		public void CopyTo(T[] array, int arrayIndex)
		{
			CopyTo(0, array, arrayIndex, this.Count);
		}
		/// <summary>
		/// See <see cref="ICollection{T}.Count"/>.
		/// </summary>
		/// <value><see cref="SubsetCount"/>.</value>
		public int Count
		{
			get { return this.SubsetCount; }
		}
		/// <summary>
		/// See <see cref="ICollection{T}.IsReadOnly"/>.
		/// </summary>
		public bool IsReadOnly
		{
			get { return this.List.IsReadOnly; }
		}
		/// <summary>
		/// See <see cref="ICollection{T}.Remove"/>.
		/// </summary>
		public bool Remove(T item)
		{
			int index = IndexOf(item);
			if (index < 0)
				return false;
			try
			{
				RemoveAt(index);
			}
			catch (Exception)
			{
				return false;
			}
			return true;
		}

		#endregion

		#region IEnumerable<T> Members

		/// <summary>
		/// See <see cref="IEnumerable{T}.GetEnumerator"/>.
		/// </summary>
		public IEnumerator<T> GetEnumerator()
		{
			int endIndex = this.TEndIndex;
			for (int i = this.TStartIndex; i <= endIndex; i++)
			{
				yield return this.List[i];
			}
		}

		#endregion

		#region IEnumerable Members

		/// <summary>
		/// See <see cref="IEnumerable.GetEnumerator"/>.
		/// </summary>
		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion

		/// <summary>
		/// See <see cref="List"/>.
		/// </summary>
		private IList<T> _list;
	}
}
